
local Document = _ENV.import("./window.lua").Document;
local Window = _ENV.import("./window.lua").Window;

local g_component_options = {};  -- 全局组件选项

local Component = commonlib.inherit(Document);
Component:Property("WatchObject");
Component:Property("Options");

function Component:ctor()
    self.m_tag_component_options = {};
end

function Component:Init(window, rawdocument, env)
    Component._super.Init(self, window, rawdocument, env);
    self:SetWatchObject(self:GetVue():NewWatchObject(env));
    self:SetOptions(self:GetComponentOptions(self:GetTagName()) or {});

    local data = self:GetData();
    local watch_object = self:GetWatchObject();
    for key, value in pairs(data) do watch_object:Set(key, value) end

    local methods = self:GetMethods();
    for key, value in pairs(methods) do env[key] = value end

    env.get = function(key) return self:GetWatchObject():Get(key) end
    env.set = function(key, value) return self:GetWatchObject():Set(key, value) end
    env.watch = function(expr, callback, default_value) return self:GetWatchObject():NewWatchExpr(expr, callback, default_value) end

    return self;
end

function Component:GetVue()
    return self:GetWindow();
end

function Component:GetData()
    local data = self:GetOptions().data;
    if (type(data) == "function") then return data() end
    return type(data) == "table" and data or {};
end

function Component:GetMethods()
    local methods = self:GetOptions().methods;
    return type(methods) == "table" and methods or {};
end

function Component:RegisterComponentOptions(options)
    if (type(options) ~= "table" or not options.tagname) then return end
    self.m_tag_component_options[options.tagname] = options;
end

function Component:GetComponentOptions(tagname)
    return self.m_tag_component_options[tagname] or self:GetWindow():GetComponentOptions(tagname);
end

function Component:Load()
    Component._super.Load(self);
    self:Compile();
end

function Component:Compile()
    local function CompileElement(element, watch_object)
        local attrs = element.m_attrs;
        local vfor = attrs["v-for"];

        if (vfor) then
            element:SetAttrValue("v-for", nil);
            self:Compile_VFor(vfor, element, watch_object, CompileElement);
            return;
        end

        for attr_key, attr_value in pairs(attrs) do
            if (attr_key == "v-if") then
                self:WatchExpr(attr_value, false, function(newval)
                    element:SetVisible(newval);
                end, watch_object);
            elseif (attr_key == "v-text") then
                self:WatchExpr(attr_value, attrs[attr_key], function(val)
                    element:SetAttrValue("text", val or "");
                end, watch_object);
            elseif (string.sub(attr_key, 1, 7) == "v-bind:") then
                local attrName = string.sub(attr_key, 8);
                local attrExpr = "return " .. attr_value;
                local attrDefaultValue = attrs[attrName];
                self:WatchExpr(attrExpr, attrDefaultValue, function(val)
                    element:SetAttrValue(attrName, val);
                end, watch_object);
            end
        end
        element:ForEachChildren(function(children)
            CompileElement(children, watch_object)
        end);
    end
    CompileElement(self:GetElement(), self:GetWatchObject());
end

function Component:Compile_VFor(vfor, element, watch_object, compile_element)
    element:SetVisible(false);

    local inIndex = string.find(vfor, " in ", 1, true);
    if (not inIndex) then return end
    local keyexpr = string.sub(vfor, 1, inIndex -1);
    local listexpr = string.sub(vfor, inIndex + 4);

    keyexpr = commonlib.trim(keyexpr);
    listexpr = commonlib.trim(listexpr);
    if (keyexpr == "" or listexpr == "") then return end

    local val, key = string.match(keyexpr, "(%a%w-)%s*,%s*(%a%w+)");
    if (not val) then val = string.gsub(keyexpr, "[,%s]*$", "") end
    key = key or "index";

    local parent_element = self:GetElementByKey(element:GetParentKey());
    if (parent_element == nil) then return end
    local clone_elements, clone_watch_objects = {}, {};
    self:WatchExpr(listexpr, nil, function(list)
        if (type(list) ~= "table" or (#list) == 0) then return end
        for _, clone_element in ipairs(clone_elements) do
            parent_element:RemoveChildren(clone_element);
        end
        local previous_element = element;
        for index, item in ipairs(list) do
            local clone_watch_object = clone_watch_objects[index] or watch_object:NewInheritWatchObject();
            clone_watch_object:Set(key, index);
            clone_watch_object:Set(val, item);
            clone_watch_objects[index] = clone_watch_object;
            local clone_element = clone_elements[index] or self:CloneElement(element, true);
            clone_elements[index] = clone_element;
            clone_element:SetVisible(true);
            parent_element:InsertChildren(clone_element, previous_element, true);
            previous_element = clone_element;
            compile_element(clone_element, clone_watch_object);
        end
    end, watch_object);
end

function Component:WatchExpr(expr, default_value, callback, watch_object)
    watch_object = watch_object or self:GetWatchObject();
    watch_object:NewWatchExpr("return " .. expr, callback, default_value);
end

local Vue = commonlib.inherit(Window);

function Vue:ctor()
    self.m_root_watch_object = commonlib.NewRootWatchObject();
    self.m_tag_component_options = {};

    local env = self:GetEnv();
    env.get = function(key) return self.m_root_watch_object:Get(key) end
    env.set = function(key, value) return self.m_root_watch_object:Set(key, value) end
    env.watch = function(expr, callback, default_value) return self.m_root_watch_object:NewWatchExpr(expr, callback, default_value) end
end

function Vue:NewDocument(rawdocument, env)
    return Component:new():Init(self, rawdocument, env);
end

function Vue:NewWatchObject(data)
    return self.m_root_watch_object:NewInheritWatchObject(data);
end

function Vue:RegisterComponentOptions(options)
    if (type(options) ~= "table" or not options.tagname) then return end
    self.m_tag_component_options[options.agname] = options;
end

function Vue:GetComponentOptions(tagname)
    return self.m_tag_component_options[tagname] or g_component_options[tagname];
end

function Vue.Component(options)
    if (type(options) ~= "table" or not options.tagname) then return end
    g_component_options[options.tagname] = options;
end

commonlib.ShowVueWindow = function(options, G)
    local window = Vue:new():Init(options, G);
    window:Show();
    return window;
end

-- local vue = Vue:new():Init();

    -- -- 维持脚本和样式原本格式
    -- template = string.gsub(template, "[\r\n]<script(.-)>(.-)[\r\n]</script>", "<script%1>\n<![CDATA[\n%2\n]]>\n</script>");
    -- template = string.gsub(template, "[\r\n]<style(.-)>(.-)[\r\n]</style>", "<style%1>\n<![CDATA[\n%2\n]]>\n</style>");